Data Binding
When building web applications, we often want to sync a bit of state to a particular form input. For example, a "username" field should be bound to the value of a username
state variable.
This is commonly known as “data binding”. Most front-end frameworks offer a way to bind a particular bit of state to a particular form control.
Here's what this typically looks like in React:
Code Playground
If you'd like, take a few minutes and poke at this. What happens if you change or remove the value
or onChange
attributes?
Let's dig into it:
Video Summary
- The
value
attribute works differently in React than it does in HTML.- In HTML,
value
sets the default value, and can be edited - In React,
value
locks the input to the specified value, and it becomes read-only.
- By setting
value
equal to oursearchTerm
state variable, we ensure that the input will always display the search term. - Let's add a button that sets the search term to a random number. We see that by clicking this button, the input will be updated to show this new value.
- Essentially, our data binding is 50% complete. The input will always show the value of the
searchTerm
state, even when that value is updated, but the state can't be changed by the input. The binding is only one way. - When we add an
onChange
handler, we see that the input actually does briefly update to show the new value. The problem is that React will undo that change immediately after the change event fires, before the browser has even had the time to complete a single repaint. - We can call
setSearchTerm
with the input's current value as a way to persist that edit. When React re-renders, the input will be updated to show the updated value held in state. - Do we really need
value
? It seems to work with just the onChange listener!- Well, this is also a one-way data binding, but it's the opposite of the situation we saw before. The input can update the state, but the state can't update the input. If we click our random number generator button, the state is updated to a new number, but the input doesn't update to show this new value.
- Similarly, if our state has an initial value like
cats
, we won't see that initial value unless we control the input by settingvalue={searchTerm}
.
Here's the sandbox from the end of the video:
Code Playground
Controlled vs. Uncontrolled inputs
When we set the value
attribute on a form input, we tell React that it should be a controlled input. The word “controlled” has a specific definition in React; it means that React is managing the input.
By contrast, if we don't set value
, the input is an uncontrolled input. This means that React doesn't do any management.
There's a golden rule here: An input should always either be controlled or uncontrolled. React doesn't like when we flip an element from one to the other.
This can lead to a common footgun. Let's learn about it, so that we can avoid it.
Consider this situation:
Code Playground
Try typing in the text input, and then switch to the “Console” tab. You should see a warning that begins like this:
Warning: A component is changing an uncontrolled input to be controlled.
This is weird, right? This input is controlled! We're setting value={username}
from the very first render!
Here's the problem: username
is undefined at first, since there is no default value in the state hook. Here's a simplified version of what we're doing:
const username = undefined;
<input type="text" id="username" value={username} onChange={event => { setUsername(event.target.value); }}/>
When we set value
to undefined
, it's the same as not setting it at all. React will treat the input as an uncontrolled input.
When the user starts typing in the input, the onChange
event updates the value of username
from undefined
to a string. And so, React flips the element to a controlled input, and raises the warning.
Here's how to solve the problem: We always want to make sure we're passing a defined value
. We can do this by initializing username
to an empty string:
// 🚫 Incorrect. `username` will flip from `undefined` to a string:const [username, setUsername] = React.useState();
// ✅ Correct. `username` will always be a string:const [username, setUsername] = React.useState('');
With this change, our input is being controlled by React state from the very first render, since we're always passing a defined value. Even though empty strings are considered falsy 👀, they still “count” when it comes to controlling React inputs.